Predicting Benign and Malignant Classes in Mammograms

Jay Narhan

May 2017

This is an extension to the detection of abnormalities case (see notebook JN_BC_Diff_Detection.ipynb). Here we concern ourselves with a two class problem related to benign and malignant classifications only. The limited data has an impact on performance viz-a-viz the abnormal vs. normal assessment, yet we still achieve accuracy at approximately 78%, positive predictive value at ~79% and critically sensitivity at ~75% (the higher sensitivity, the fewer the patients who were wrongly classified as having a benign lesion when it fact it was malignant).

Note: the workbook leverages a modified version of the Synthetic Minority Over-sampling Technique (SMOTE), which looks to balance under-represented classes in the differenced dataset through creating synthetic minority cases via image augmentation (e.g. rotations, vertical and horizontal pixel shifts).

In detection cases, this had a minor improvement in performance metrics. In the diagnosis workbook, it has a major impact on improvements over balace by removal.



In [1]:
import os
import sys
import time
import numpy as np

from tqdm import tqdm

import sklearn.metrics as skm
from sklearn import metrics
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split

from skimage import color

import keras.callbacks as cb
import keras.utils.np_utils as np_utils
from keras import applications
from keras import regularizers
from keras.models import Sequential
from keras.constraints import maxnorm
from keras.preprocessing.image import ImageDataGenerator
from keras.layers.convolutional import Convolution2D, MaxPooling2D
from keras.layers import Activation, Dense, Dropout, Flatten, GaussianNoise

from matplotlib import pyplot as plt
%matplotlib inline

plt.rcParams['figure.figsize'] = (10,10)
np.set_printoptions(precision=2)

sys.path.insert(0, '../helper_modules/')
import jn_bc_helper as bc


Using Theano backend.

Reproducible Research


In [2]:
%%python
import os
os.system('python -V')
os.system('python ../helper_modules/Package_Versions.py')


scipy:           0.19.0
numpy:           1.12.1
matplotlib:      2.0.0
sklearn:         0.18.1
skimage:         0.13.0
theano:          0.9.0.dev-c697eeab84e5b8a74908da654b66ec9eca4f1291
tensorflow:      0.10.0
keras:           2.0.3
Python 2.7.13 :: Continuum Analytics, Inc.
Using Theano backend.

In [3]:
SEED = 7
np.random.seed(SEED)

DATA_DIR = '/Users/jnarhan/Dropbox/Breast_Cancer_Data/Data_Differenced/ALL_IMGS/'
AUG_DIR   = '/Users/jnarhan/Dropbox/Breast_Cancer_Data/Data_Differenced/AUG_DIAGNOSIS_IMGS/'
meta_file = '../../Meta_Data_Files/meta_data_diagnosis.csv'
PATHO_INX = 4    # Column number of pathology label in meta_file
FILE_INX  = 1    # Column number of File name in meta_file

meta_data, cls_cnts = tqdm( bc.load_meta(meta_file, patho_idx=PATHO_INX, file_idx=FILE_INX,
                                   balanceByRemoval=False, verbose=True) )

bc.pprint('Loading data')
cats = bc.bcLabels(['benign', 'malignant'])

# For smaller images supply tuple argument for a parameter 'imgResize':
# X_data, Y_data = bc.load_data(meta_data, DATA_DIR, cats, imgResize=(150,150)) 
X_data, Y_data = tqdm( bc.load_data(meta_data, DATA_DIR, cats) )


100%|██████████| 2/2 [00:00<00:00, 10994.24it/s]
----------------
Before Balancing
----------------
benign    : 569
malignant : 812
------------
Loading data
------------
100%|██████████| 2/2 [00:00<00:00, 12748.64it/s]

Class Balancing

Here - I look at a modified version of SMOTE, growing the under-represented class via synthetic augmentation, until there is a balance among the categories:


In [4]:
datagen = ImageDataGenerator(rotation_range=5, width_shift_range=.01, height_shift_range=0.01,
                             data_format='channels_first')

In [5]:
X_data, Y_data = bc.balanceViaSmote(cls_cnts, meta_data, DATA_DIR, AUG_DIR, cats, 
                                    datagen, X_data, Y_data, seed=SEED, verbose=True)


---------------
After Balancing
---------------
malignant : 812
benign    : 812

Create the Training and Test Datasets


In [6]:
X_train, X_test, Y_train, Y_test = train_test_split(X_data, Y_data,
                                                    test_size=0.20, # deviation given small data set
                                                    random_state=SEED,
                                                    stratify=zip(*Y_data)[0])

print 'Size of X_train: {:>5}'.format(len(X_train))
print 'Size of X_test: {:>5}'.format(len(X_test))
print 'Size of Y_train: {:>5}'.format(len(Y_train))
print 'Size of Y_test: {:>5}'.format(len(Y_test))

print X_train.shape
print X_test.shape
print Y_train.shape
print Y_test.shape

data = [X_train, X_test, Y_train, Y_test]


Size of X_train:  1299
Size of X_test:   325
Size of Y_train:  1299
Size of Y_test:   325
(1299, 255, 255)
(325, 255, 255)
(1299, 1)
(325, 1)

Support Vector Machine Model


In [7]:
X_train_svm = X_train.reshape( (X_train.shape[0], -1)) 
X_test_svm  = X_test.reshape( (X_test.shape[0], -1))

In [8]:
SVM_model = SVC(gamma=0.001)
SVM_model.fit( X_train_svm, Y_train)


/Users/jnarhan/miniconda2/envs/bc_venv/lib/python2.7/site-packages/sklearn/utils/validation.py:526: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
  y = column_or_1d(y, warn=True)
Out[8]:
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape=None, degree=3, gamma=0.001, kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)

In [9]:
predictOutput = SVM_model.predict(X_test_svm)
svm_acc = metrics.accuracy_score(y_true=Y_test, y_pred=predictOutput)

print 'SVM Accuracy: {: >7.2f}%'.format(svm_acc * 100)
print 'SVM Error: {: >10.2f}%'.format(100 - svm_acc * 100)


SVM Accuracy:   77.85%
SVM Error:      22.15%

In [10]:
svm_matrix = skm.confusion_matrix(y_true=Y_test, y_pred=predictOutput)
numBC = bc.reverseDict(cats)
class_names = numBC.values()

plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(svm_matrix, classes=class_names, normalize=True, 
                         title='SVM Normalized Confusion Matrix Using Differencing \n')
plt.tight_layout()
plt.savefig('../../figures/jn_SVM_Diagnosis_CM_20170530.png', dpi=100)


Normalized confusion matrix
[[ 0.8   0.2 ]
 [ 0.24  0.76]]

In [11]:
plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(svm_matrix, classes=class_names, normalize=False, 
                         title='SVM Normalized Confusion Matrix Using Differencing \n')
plt.tight_layout()


Confusion matrix, without normalization
[[129  33]
 [ 39 124]]

In [12]:
bc.cat_stats(svm_matrix)


Out[12]:
{'Accuracy': 77.85,
 'F1': 0.78,
 'NPV': 76.79,
 'PPV': 78.98,
 'Sensitivity': 76.07,
 'Specificity': 79.63}

CNN Modelling Using VGG16 in Transfer Learning


In [13]:
def VGG_Prep(img_data):
    """
    :param img_data: training or test images of shape [#images, height, width]
    :return: the array transformed to the correct shape for the VGG network
                shape = [#images, height, width, 3] transforms to rgb and reshapes
    """
    images = np.zeros([len(img_data), img_data.shape[1], img_data.shape[2], 3])
    for i in range(0, len(img_data)):
        im = (img_data[i] * 255)        # Original imagenet images were not rescaled
        im = color.gray2rgb(im)
        images[i] = im
    return(images)

In [14]:
def vgg16_bottleneck(data, modelPath, fn_train_feats, fn_train_lbls, fn_test_feats, fn_test_lbls):
    # Loading data
    X_train, X_test, Y_train, Y_test = data
    
    print('Preparing the Training Data for the VGG_16 Model.')
    X_train = VGG_Prep(X_train)
    print('Preparing the Test Data for the VGG_16 Model')
    X_test = VGG_Prep(X_test)
        
    print('Loading the VGG_16 Model')
    # "model" excludes top layer of VGG16:
    model = applications.VGG16(include_top=False, weights='imagenet') 
        
    # Generating the bottleneck features for the training data
    print('Evaluating the VGG_16 Model on the Training Data')
    bottleneck_features_train = model.predict(X_train)
    
    # Saving the bottleneck features for the training data
    featuresTrain = os.path.join(modelPath, fn_train_feats)
    labelsTrain = os.path.join(modelPath, fn_train_lbls)
    print('Saving the Training Data Bottleneck Features.')
    np.save(open(featuresTrain, 'wb'), bottleneck_features_train)
    np.save(open(labelsTrain, 'wb'), Y_train)

    # Generating the bottleneck features for the test data
    print('Evaluating the VGG_16 Model on the Test Data')
    bottleneck_features_test = model.predict(X_test)
    
    # Saving the bottleneck features for the test data
    featuresTest = os.path.join(modelPath, fn_test_feats)
    labelsTest = os.path.join(modelPath, fn_test_lbls)
    print('Saving the Test Data Bottleneck Feaures.')
    np.save(open(featuresTest, 'wb'), bottleneck_features_test)
    np.save(open(labelsTest, 'wb'), Y_test)

In [15]:
# Locations for the bottleneck and labels files that we need
train_bottleneck = '2Class_Lesions_VGG16_bottleneck_features_train.npy'
train_labels     = '2Class_Lesions_VGG16_labels_train.npy'
test_bottleneck  = '2Class_Lesions_VGG16_bottleneck_features_test.npy'
test_labels      = '2Class_Lesions_VGG16_labels_test.npy'
modelPath = os.getcwd()

top_model_weights_path = './weights/'

np.random.seed(SEED)
vgg16_bottleneck(data, modelPath, train_bottleneck, train_labels, test_bottleneck, test_labels)


Preparing the Training Data for the VGG_16 Model.
Preparing the Test Data for the VGG_16 Model
Loading the VGG_16 Model
Evaluating the VGG_16 Model on the Training Data
Saving the Training Data Bottleneck Features.
Evaluating the VGG_16 Model on the Test Data
Saving the Test Data Bottleneck Feaures.

In [16]:
def train_top_model(train_feats, train_lab, test_feats, test_lab, model_path, model_save, epoch = 50, batch = 64):
    start_time = time.time()
    
    train_bottleneck = os.path.join(model_path, train_feats)
    train_labels = os.path.join(model_path, train_lab)
    test_bottleneck = os.path.join(model_path, test_feats)
    test_labels = os.path.join(model_path, test_lab)
    
    history = bc.LossHistory()
    
    X_train = np.load(train_bottleneck)
    Y_train = np.load(train_labels)
    Y_train = np_utils.to_categorical(Y_train, num_classes=2)
    
    X_test = np.load(test_bottleneck)
    Y_test = np.load(test_labels)
    Y_test = np_utils.to_categorical(Y_test, num_classes=2)

    model = Sequential()
    model.add(Flatten(input_shape=X_train.shape[1:]))
    model.add( Dropout(0.7))
    
    model.add( Dense(256, activation='relu', kernel_constraint= maxnorm(3.)) )
    model.add( Dropout(0.5))
    
    # Softmax for probabilities for each class at the output layer
    model.add( Dense(2, activation='softmax'))
    
    model.compile(optimizer='rmsprop',  # adadelta
                  loss='binary_crossentropy', 
                  metrics=['accuracy'])

    model.fit(X_train, Y_train,
              epochs=epoch,
              batch_size=batch,
              callbacks=[history],
              validation_data=(X_test, Y_test),
              verbose=2)
    
    print "Training duration : {0}".format(time.time() - start_time)
    score = model.evaluate(X_test, Y_test, batch_size=16, verbose=2)

    print "Network's test score [loss, accuracy]: {0}".format(score)
    print 'CNN Error: {:.2f}%'.format(100 - score[1] * 100)
    
    bc.save_model(model_save, model, "jn_VGG16_Diagnosis_top_weights.h5")
    
    return model, history.losses, history.acc, score

In [17]:
np.random.seed(SEED)
(trans_model, loss_cnn, acc_cnn, test_score_cnn) = train_top_model(train_feats=train_bottleneck,
                                                                   train_lab=train_labels, 
                                                                   test_feats=test_bottleneck, 
                                                                   test_lab=test_labels,
                                                                   model_path=modelPath, 
                                                                   model_save=top_model_weights_path,
                                                                   epoch=100)
plt.figure(figsize=(10,10))
bc.plot_losses(loss_cnn, acc_cnn)
plt.savefig('../../figures/epoch_figures/jn_Transfer_Diagnosis_20170530.png', dpi=100)


Train on 1299 samples, validate on 325 samples
Epoch 1/100
3s - loss: 7.1627 - acc: 0.5343 - val_loss: 3.7051 - val_acc: 0.7385
Epoch 2/100
3s - loss: 5.3945 - acc: 0.6274 - val_loss: 5.9261 - val_acc: 0.6123
Epoch 3/100
3s - loss: 5.3563 - acc: 0.6459 - val_loss: 4.1449 - val_acc: 0.7231
Epoch 4/100
3s - loss: 5.1612 - acc: 0.6497 - val_loss: 3.5158 - val_acc: 0.7569
Epoch 5/100
3s - loss: 4.8320 - acc: 0.6790 - val_loss: 3.4186 - val_acc: 0.7692
Epoch 6/100
3s - loss: 4.8621 - acc: 0.6767 - val_loss: 3.5920 - val_acc: 0.7662
Epoch 7/100
3s - loss: 4.8593 - acc: 0.6790 - val_loss: 4.0513 - val_acc: 0.7292
Epoch 8/100
3s - loss: 4.5874 - acc: 0.6975 - val_loss: 3.4540 - val_acc: 0.7723
Epoch 9/100
3s - loss: 4.5285 - acc: 0.7021 - val_loss: 3.4220 - val_acc: 0.7754
Epoch 10/100
3s - loss: 4.4281 - acc: 0.7059 - val_loss: 3.9333 - val_acc: 0.7354
Epoch 11/100
3s - loss: 4.3052 - acc: 0.7152 - val_loss: 5.8370 - val_acc: 0.6215
Epoch 12/100
3s - loss: 4.9382 - acc: 0.6798 - val_loss: 3.9867 - val_acc: 0.7415
Epoch 13/100
3s - loss: 4.4885 - acc: 0.7098 - val_loss: 3.6520 - val_acc: 0.7631
Epoch 14/100
3s - loss: 4.1294 - acc: 0.7290 - val_loss: 3.4442 - val_acc: 0.7723
Epoch 15/100
3s - loss: 4.0038 - acc: 0.7383 - val_loss: 3.2400 - val_acc: 0.7815
Epoch 16/100
3s - loss: 3.9199 - acc: 0.7406 - val_loss: 3.5824 - val_acc: 0.7569
Epoch 17/100
3s - loss: 4.2513 - acc: 0.7198 - val_loss: 4.1601 - val_acc: 0.7138
Epoch 18/100
3s - loss: 3.9534 - acc: 0.7429 - val_loss: 3.4151 - val_acc: 0.7785
Epoch 19/100
3s - loss: 3.8393 - acc: 0.7498 - val_loss: 3.5407 - val_acc: 0.7692
Epoch 20/100
3s - loss: 4.1813 - acc: 0.7267 - val_loss: 3.4304 - val_acc: 0.7815
Epoch 21/100
3s - loss: 4.3474 - acc: 0.7182 - val_loss: 3.6184 - val_acc: 0.7631
Epoch 22/100
3s - loss: 4.1499 - acc: 0.7267 - val_loss: 3.6578 - val_acc: 0.7600
Epoch 23/100
3s - loss: 3.7705 - acc: 0.7498 - val_loss: 3.5992 - val_acc: 0.7631
Epoch 24/100
3s - loss: 3.7227 - acc: 0.7575 - val_loss: 3.4289 - val_acc: 0.7754
Epoch 25/100
3s - loss: 3.8962 - acc: 0.7413 - val_loss: 3.6970 - val_acc: 0.7662
Epoch 26/100
3s - loss: 4.1567 - acc: 0.7313 - val_loss: 3.5421 - val_acc: 0.7754
Epoch 27/100
3s - loss: 3.7409 - acc: 0.7552 - val_loss: 3.3937 - val_acc: 0.7846
Epoch 28/100
3s - loss: 3.6416 - acc: 0.7644 - val_loss: 3.4826 - val_acc: 0.7754
Epoch 29/100
3s - loss: 3.8639 - acc: 0.7483 - val_loss: 3.2561 - val_acc: 0.7846
Epoch 30/100
3s - loss: 3.8469 - acc: 0.7506 - val_loss: 3.2724 - val_acc: 0.7846
Epoch 31/100
3s - loss: 3.7983 - acc: 0.7467 - val_loss: 3.1979 - val_acc: 0.7877
Epoch 32/100
3s - loss: 3.7067 - acc: 0.7567 - val_loss: 3.6630 - val_acc: 0.7538
Epoch 33/100
3s - loss: 3.7323 - acc: 0.7560 - val_loss: 3.2732 - val_acc: 0.7908
Epoch 34/100
3s - loss: 3.6670 - acc: 0.7590 - val_loss: 3.4621 - val_acc: 0.7692
Epoch 35/100
3s - loss: 3.9116 - acc: 0.7475 - val_loss: 3.0638 - val_acc: 0.8031
Epoch 36/100
4s - loss: 3.8999 - acc: 0.7444 - val_loss: 3.4676 - val_acc: 0.7692
Epoch 37/100
4s - loss: 3.5827 - acc: 0.7652 - val_loss: 3.0618 - val_acc: 0.7969
Epoch 38/100
4s - loss: 3.7829 - acc: 0.7529 - val_loss: 4.2980 - val_acc: 0.7231
Epoch 39/100
4s - loss: 3.9780 - acc: 0.7429 - val_loss: 3.1478 - val_acc: 0.7877
Epoch 40/100
4s - loss: 3.6573 - acc: 0.7644 - val_loss: 3.4127 - val_acc: 0.7785
Epoch 41/100
4s - loss: 3.5608 - acc: 0.7698 - val_loss: 3.1321 - val_acc: 0.7908
Epoch 42/100
4s - loss: 3.6110 - acc: 0.7675 - val_loss: 3.4487 - val_acc: 0.7785
Epoch 43/100
4s - loss: 3.6203 - acc: 0.7660 - val_loss: 3.1375 - val_acc: 0.7969
Epoch 44/100
4s - loss: 3.5259 - acc: 0.7714 - val_loss: 3.1292 - val_acc: 0.8000
Epoch 45/100
4s - loss: 3.5963 - acc: 0.7706 - val_loss: 3.1191 - val_acc: 0.7938
Epoch 46/100
4s - loss: 3.6738 - acc: 0.7667 - val_loss: 3.5966 - val_acc: 0.7723
Epoch 47/100
4s - loss: 3.5400 - acc: 0.7744 - val_loss: 3.2626 - val_acc: 0.7938
Epoch 48/100
4s - loss: 3.4635 - acc: 0.7760 - val_loss: 3.4272 - val_acc: 0.7815
Epoch 49/100
4s - loss: 3.5384 - acc: 0.7752 - val_loss: 3.5180 - val_acc: 0.7754
Epoch 50/100
4s - loss: 3.5290 - acc: 0.7714 - val_loss: 3.2863 - val_acc: 0.7846
Epoch 51/100
4s - loss: 3.5358 - acc: 0.7706 - val_loss: 3.4682 - val_acc: 0.7754
Epoch 52/100
4s - loss: 3.7866 - acc: 0.7544 - val_loss: 3.7428 - val_acc: 0.7600
Epoch 53/100
4s - loss: 3.8990 - acc: 0.7467 - val_loss: 3.3113 - val_acc: 0.7785
Epoch 54/100
4s - loss: 3.5425 - acc: 0.7737 - val_loss: 3.2767 - val_acc: 0.7815
Epoch 55/100
4s - loss: 3.3435 - acc: 0.7860 - val_loss: 3.2222 - val_acc: 0.7877
Epoch 56/100
4s - loss: 3.6717 - acc: 0.7614 - val_loss: 3.3057 - val_acc: 0.7846
Epoch 57/100
4s - loss: 3.5620 - acc: 0.7714 - val_loss: 3.5439 - val_acc: 0.7754
Epoch 58/100
4s - loss: 3.3045 - acc: 0.7844 - val_loss: 3.3266 - val_acc: 0.7877
Epoch 59/100
4s - loss: 3.2940 - acc: 0.7837 - val_loss: 3.5293 - val_acc: 0.7754
Epoch 60/100
4s - loss: 3.4387 - acc: 0.7806 - val_loss: 3.3179 - val_acc: 0.7846
Epoch 61/100
4s - loss: 3.2139 - acc: 0.7906 - val_loss: 3.6944 - val_acc: 0.7600
Epoch 62/100
4s - loss: 3.4964 - acc: 0.7744 - val_loss: 3.5726 - val_acc: 0.7662
Epoch 63/100
4s - loss: 3.3884 - acc: 0.7798 - val_loss: 3.3418 - val_acc: 0.7877
Epoch 64/100
4s - loss: 3.5469 - acc: 0.7683 - val_loss: 3.3533 - val_acc: 0.7815
Epoch 65/100
5s - loss: 3.1386 - acc: 0.7960 - val_loss: 3.4110 - val_acc: 0.7785
Epoch 66/100
5s - loss: 3.5844 - acc: 0.7683 - val_loss: 3.6983 - val_acc: 0.7662
Epoch 67/100
5s - loss: 3.3817 - acc: 0.7821 - val_loss: 4.6038 - val_acc: 0.7046
Epoch 68/100
5s - loss: 3.3017 - acc: 0.7860 - val_loss: 3.1750 - val_acc: 0.7846
Epoch 69/100
5s - loss: 3.6882 - acc: 0.7606 - val_loss: 3.6783 - val_acc: 0.7600
Epoch 70/100
5s - loss: 3.5151 - acc: 0.7714 - val_loss: 3.5831 - val_acc: 0.7723
Epoch 71/100
5s - loss: 3.5634 - acc: 0.7683 - val_loss: 3.4144 - val_acc: 0.7754
Epoch 72/100
5s - loss: 3.6082 - acc: 0.7691 - val_loss: 3.2882 - val_acc: 0.7846
Epoch 73/100
5s - loss: 3.1836 - acc: 0.7975 - val_loss: 3.3624 - val_acc: 0.7846
Epoch 74/100
5s - loss: 3.2789 - acc: 0.7875 - val_loss: 3.4709 - val_acc: 0.7785
Epoch 75/100
5s - loss: 3.4417 - acc: 0.7768 - val_loss: 3.4899 - val_acc: 0.7785
Epoch 76/100
5s - loss: 3.1483 - acc: 0.7937 - val_loss: 3.2593 - val_acc: 0.7846
Epoch 77/100
5s - loss: 3.3641 - acc: 0.7814 - val_loss: 3.3210 - val_acc: 0.7754
Epoch 78/100
5s - loss: 3.3704 - acc: 0.7814 - val_loss: 3.3097 - val_acc: 0.7846
Epoch 79/100
5s - loss: 3.8080 - acc: 0.7552 - val_loss: 3.2456 - val_acc: 0.7908
Epoch 80/100
5s - loss: 3.6634 - acc: 0.7644 - val_loss: 3.3722 - val_acc: 0.7846
Epoch 81/100
5s - loss: 3.4865 - acc: 0.7760 - val_loss: 3.4745 - val_acc: 0.7785
Epoch 82/100
5s - loss: 3.5261 - acc: 0.7714 - val_loss: 3.2064 - val_acc: 0.8000
Epoch 83/100
5s - loss: 3.4932 - acc: 0.7775 - val_loss: 3.6451 - val_acc: 0.7662
Epoch 84/100
5s - loss: 3.3434 - acc: 0.7852 - val_loss: 3.4261 - val_acc: 0.7815
Epoch 85/100
5s - loss: 3.4259 - acc: 0.7806 - val_loss: 3.1601 - val_acc: 0.7969
Epoch 86/100
5s - loss: 3.2451 - acc: 0.7883 - val_loss: 3.1443 - val_acc: 0.8000
Epoch 87/100
5s - loss: 3.1487 - acc: 0.7975 - val_loss: 3.6178 - val_acc: 0.7692
Epoch 88/100
5s - loss: 3.0331 - acc: 0.8060 - val_loss: 3.4016 - val_acc: 0.7815
Epoch 89/100
5s - loss: 3.2255 - acc: 0.7921 - val_loss: 3.1734 - val_acc: 0.7969
Epoch 90/100
5s - loss: 3.2535 - acc: 0.7921 - val_loss: 3.1740 - val_acc: 0.7877
Epoch 91/100
5s - loss: 3.2255 - acc: 0.7906 - val_loss: 3.2359 - val_acc: 0.7938
Epoch 92/100
5s - loss: 3.4179 - acc: 0.7783 - val_loss: 3.7485 - val_acc: 0.7631
Epoch 93/100
5s - loss: 3.4147 - acc: 0.7791 - val_loss: 3.3750 - val_acc: 0.7877
Epoch 94/100
5s - loss: 3.2228 - acc: 0.7952 - val_loss: 3.3136 - val_acc: 0.7815
Epoch 95/100
5s - loss: 3.0004 - acc: 0.8083 - val_loss: 3.5978 - val_acc: 0.7692
Epoch 96/100
5s - loss: 3.0780 - acc: 0.8022 - val_loss: 3.4624 - val_acc: 0.7815
Epoch 97/100
5s - loss: 3.3187 - acc: 0.7883 - val_loss: 3.1340 - val_acc: 0.7938
Epoch 98/100
5s - loss: 3.3466 - acc: 0.7837 - val_loss: 3.3758 - val_acc: 0.7877
Epoch 99/100
5s - loss: 3.3782 - acc: 0.7829 - val_loss: 3.5546 - val_acc: 0.7723
Epoch 100/100
5s - loss: 3.0804 - acc: 0.8045 - val_loss: 3.4122 - val_acc: 0.7815
Training duration : 452.662991047
Network's test score [loss, accuracy]: [3.4122213204863945, 0.78153846172186048]
CNN Error: 21.85%
Model and Weights Saved to Disk
<matplotlib.figure.Figure at 0x147665990>

In [18]:
print 'Transfer Learning CNN Accuracy: {: >7.2f}%'.format(test_score_cnn[1] * 100)
print 'Transfer Learning CNN Error: {: >10.2f}%'.format(100 - test_score_cnn[1] * 100)

predictOutput = bc.predict(trans_model, np.load(test_bottleneck))
trans_matrix = skm.confusion_matrix(y_true=Y_test, y_pred=predictOutput)

plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(trans_matrix, classes=class_names, normalize=True,
                         title='Transfer CNN Normalized Confusion Matrix Using Differencing \n')
plt.tight_layout()
plt.savefig('../../figures/TMP_jn_Transfer_Diagnosis_CM_20170526.png', dpi=100)


Transfer Learning CNN Accuracy:   78.15%
Transfer Learning CNN Error:      21.85%
Normalized confusion matrix
[[ 0.81  0.19]
 [ 0.25  0.75]]

In [19]:
plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(trans_matrix, classes=class_names, normalize=False,
                         title='Transfer CNN Normalized Confusion Matrix Using Differencing \n')
plt.tight_layout()


Confusion matrix, without normalization
[[131  31]
 [ 40 123]]

In [20]:
bc.cat_stats(trans_matrix)


Out[20]:
{'Accuracy': 78.15,
 'F1': 0.78,
 'NPV': 76.61,
 'PPV': 79.87,
 'Sensitivity': 75.46,
 'Specificity': 80.86}

Core CNN Modelling

Prep and package the data for Keras processing:


In [21]:
data = [X_train, X_test, Y_train, Y_test]
X_train, X_test, Y_train, Y_test = bc.prep_data(data, cats)
data = [X_train, X_test, Y_train, Y_test]

print X_train.shape
print X_test.shape
print Y_train.shape
print Y_test.shape


Prep data for NNs ...
Data Prepped for Neural Nets.
(1299, 1, 255, 255)
(325, 1, 255, 255)
(1299, 2)
(325, 2)

Heavy Regularization


In [22]:
def diff_model_v7_reg(numClasses, input_shape=(3, 150,150), add_noise=False, noise=0.01, verbose=False):
    model = Sequential()
    if (add_noise):
        model.add( GaussianNoise(noise, input_shape=input_shape))
        model.add( Convolution2D(filters=16, 
                                 kernel_size=(5,5), 
                                 data_format='channels_first',
                                 padding='same',
                                 activation='relu'))
    else:
        model.add( Convolution2D(filters=16, 
                                 kernel_size=(5,5), 
                                 data_format='channels_first',
                                 padding='same',
                                 activation='relu',
                                 input_shape=input_shape))
    model.add( Dropout(0.7))
    
    model.add( Convolution2D(filters=32, kernel_size=(3,3), 
                             data_format='channels_first', padding='same', activation='relu'))
    model.add( MaxPooling2D(pool_size= (2,2), data_format='channels_first'))
    model.add( Dropout(0.4))
    model.add( Convolution2D(filters=32, kernel_size=(3,3), 
                             data_format='channels_first', activation='relu'))
    
    model.add( Convolution2D(filters=64, kernel_size=(3,3), 
                             data_format='channels_first', padding='same', activation='relu',
                             kernel_regularizer=regularizers.l2(0.01)))
    model.add( MaxPooling2D(pool_size= (2,2), data_format='channels_first'))
    model.add( Convolution2D(filters=64, kernel_size=(3,3), 
                             data_format='channels_first', activation='relu',
                             kernel_regularizer=regularizers.l2(0.01)))
    model.add( Dropout(0.4))
    
    model.add( Convolution2D(filters=128, kernel_size=(3,3), 
                             data_format='channels_first', padding='same', activation='relu',
                             kernel_regularizer=regularizers.l2(0.01)))
    model.add( MaxPooling2D(pool_size= (2,2), data_format='channels_first'))
    
    model.add( Convolution2D(filters=128, kernel_size=(3,3), 
                             data_format='channels_first', activation='relu',
                             kernel_regularizer=regularizers.l2(0.01)))
    model.add(Dropout(0.4))
    
    model.add( Flatten())
    
    model.add( Dense(128, activation='relu', kernel_constraint= maxnorm(3.)) )
    model.add( Dropout(0.4))
    
    model.add( Dense(64, activation='relu', kernel_constraint= maxnorm(3.)) )
    model.add( Dropout(0.4))
    
    # Softmax for probabilities for each class at the output layer
    model.add( Dense(numClasses, activation='softmax'))
    
    if verbose:
        print( model.summary() )
    
    model.compile(loss='binary_crossentropy',
                  optimizer='rmsprop',
                  metrics=['accuracy'])
    return model

In [23]:
diff_model7_noise_reg = diff_model_v7_reg(len(cats),
                                          input_shape=(X_train.shape[1], X_train.shape[2], X_train.shape[3]),
                                          add_noise=True, verbose=True)


_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
gaussian_noise_1 (GaussianNo (None, 1, 255, 255)       0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 16, 255, 255)      416       
_________________________________________________________________
dropout_3 (Dropout)          (None, 16, 255, 255)      0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 32, 255, 255)      4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 32, 127, 127)      0         
_________________________________________________________________
dropout_4 (Dropout)          (None, 32, 127, 127)      0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 32, 125, 125)      9248      
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 64, 125, 125)      18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 64, 62, 62)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 64, 60, 60)        36928     
_________________________________________________________________
dropout_5 (Dropout)          (None, 64, 60, 60)        0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 128, 60, 60)       73856     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 128, 30, 30)       0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 128, 28, 28)       147584    
_________________________________________________________________
dropout_6 (Dropout)          (None, 128, 28, 28)       0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 100352)            0         
_________________________________________________________________
dense_3 (Dense)              (None, 128)               12845184  
_________________________________________________________________
dropout_7 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 64)                8256      
_________________________________________________________________
dropout_8 (Dropout)          (None, 64)                0         
_________________________________________________________________
dense_5 (Dense)              (None, 2)                 130       
=================================================================
Total params: 13,144,738
Trainable params: 13,144,738
Non-trainable params: 0
_________________________________________________________________
None

In [24]:
np.random.seed(SEED)

(cnn_model, loss_cnn, acc_cnn, test_score_cnn) = bc.run_network(model=diff_model7_noise_reg, earlyStop=False,
                                                                data=data, 
                                                                epochs=50, batch=64)
plt.figure(figsize=(10,10))
bc.plot_losses(loss_cnn, acc_cnn)
plt.savefig('../../figures/epoch_figures/jn_Core_CNN_Diagnosis_20170530.png', dpi=100)


Training model...
Train on 1299 samples, validate on 325 samples
Epoch 1/50
369s - loss: 2.6696 - acc: 0.5212 - val_loss: 1.8690 - val_acc: 0.5015
Epoch 2/50
365s - loss: 1.5044 - acc: 0.5558 - val_loss: 1.1690 - val_acc: 0.6031
Epoch 3/50
360s - loss: 1.0246 - acc: 0.5473 - val_loss: 0.8684 - val_acc: 0.5508
Epoch 4/50
362s - loss: 0.7672 - acc: 0.5920 - val_loss: 0.7076 - val_acc: 0.6338
Epoch 5/50
361s - loss: 0.6871 - acc: 0.6135 - val_loss: 0.7184 - val_acc: 0.5015
Epoch 6/50
361s - loss: 0.6549 - acc: 0.6328 - val_loss: 0.5888 - val_acc: 0.7415
Epoch 7/50
360s - loss: 0.6177 - acc: 0.6474 - val_loss: 0.7170 - val_acc: 0.5200
Epoch 8/50
358s - loss: 0.6071 - acc: 0.6759 - val_loss: 0.7046 - val_acc: 0.5692
Epoch 9/50
358s - loss: 0.6040 - acc: 0.6851 - val_loss: 0.5368 - val_acc: 0.7662
Epoch 10/50
360s - loss: 0.5643 - acc: 0.7059 - val_loss: 0.7360 - val_acc: 0.4492
Epoch 11/50
358s - loss: 0.5689 - acc: 0.7352 - val_loss: 0.7190 - val_acc: 0.5723
Epoch 12/50
359s - loss: 0.5490 - acc: 0.7283 - val_loss: 0.6811 - val_acc: 0.6215
Epoch 13/50
358s - loss: 0.5337 - acc: 0.7406 - val_loss: 0.7581 - val_acc: 0.4277
Epoch 14/50
359s - loss: 0.5112 - acc: 0.7552 - val_loss: 0.6526 - val_acc: 0.6892
Epoch 15/50
359s - loss: 0.5226 - acc: 0.7406 - val_loss: 0.7826 - val_acc: 0.5908
Epoch 16/50
360s - loss: 0.5168 - acc: 0.7360 - val_loss: 0.7968 - val_acc: 0.4215
Epoch 17/50
359s - loss: 0.4852 - acc: 0.7714 - val_loss: 0.5823 - val_acc: 0.7662
Epoch 18/50
374s - loss: 0.4914 - acc: 0.7675 - val_loss: 0.7635 - val_acc: 0.5323
Epoch 19/50
361s - loss: 0.4924 - acc: 0.7814 - val_loss: 0.6459 - val_acc: 0.6646
Epoch 20/50
361s - loss: 0.4760 - acc: 0.7714 - val_loss: 0.7000 - val_acc: 0.6615
Epoch 21/50
358s - loss: 0.4449 - acc: 0.7844 - val_loss: 0.8370 - val_acc: 0.4338
Epoch 22/50
359s - loss: 0.4576 - acc: 0.7844 - val_loss: 0.4373 - val_acc: 0.8031
Epoch 23/50
358s - loss: 0.4537 - acc: 0.7968 - val_loss: 0.8627 - val_acc: 0.4831
Epoch 24/50
360s - loss: 0.4423 - acc: 0.7952 - val_loss: 0.7929 - val_acc: 0.6246
Epoch 25/50
365s - loss: 0.4328 - acc: 0.7906 - val_loss: 0.7511 - val_acc: 0.5662
Epoch 26/50
362s - loss: 0.4407 - acc: 0.7837 - val_loss: 0.9034 - val_acc: 0.5200
Epoch 27/50
361s - loss: 0.4263 - acc: 0.8083 - val_loss: 0.6383 - val_acc: 0.7538
Epoch 28/50
358s - loss: 0.4278 - acc: 0.8122 - val_loss: 0.7308 - val_acc: 0.6862
Epoch 29/50
361s - loss: 0.4396 - acc: 0.7937 - val_loss: 0.9811 - val_acc: 0.4554
Epoch 30/50
363s - loss: 0.4207 - acc: 0.8045 - val_loss: 0.7331 - val_acc: 0.6892
Epoch 31/50
365s - loss: 0.4468 - acc: 0.7968 - val_loss: 0.8881 - val_acc: 0.5754
Epoch 32/50
363s - loss: 0.3956 - acc: 0.8222 - val_loss: 0.9549 - val_acc: 0.5108
Epoch 33/50
367s - loss: 0.4216 - acc: 0.8068 - val_loss: 0.7298 - val_acc: 0.6615
Epoch 34/50
366s - loss: 0.3796 - acc: 0.8306 - val_loss: 1.1011 - val_acc: 0.4585
Epoch 35/50
366s - loss: 0.4123 - acc: 0.8183 - val_loss: 0.8332 - val_acc: 0.6892
Epoch 36/50
365s - loss: 0.3901 - acc: 0.8206 - val_loss: 0.8468 - val_acc: 0.5569
Epoch 37/50
366s - loss: 0.3661 - acc: 0.8453 - val_loss: 0.9121 - val_acc: 0.5723
Epoch 38/50
367s - loss: 0.4191 - acc: 0.8191 - val_loss: 0.7186 - val_acc: 0.6400
Epoch 39/50
367s - loss: 0.3687 - acc: 0.8399 - val_loss: 1.1682 - val_acc: 0.5569
Epoch 40/50
366s - loss: 0.3878 - acc: 0.8283 - val_loss: 0.9414 - val_acc: 0.4985
Epoch 41/50
369s - loss: 0.3502 - acc: 0.8483 - val_loss: 0.5338 - val_acc: 0.7292
Epoch 42/50
366s - loss: 0.3924 - acc: 0.8245 - val_loss: 0.7155 - val_acc: 0.6677
Epoch 43/50
364s - loss: 0.3454 - acc: 0.8507 - val_loss: 1.0508 - val_acc: 0.5231
Epoch 44/50
364s - loss: 0.3578 - acc: 0.8445 - val_loss: 0.7825 - val_acc: 0.6954
Epoch 45/50
372s - loss: 0.3699 - acc: 0.8368 - val_loss: 0.7460 - val_acc: 0.6462
Epoch 46/50
382s - loss: 0.3517 - acc: 0.8383 - val_loss: 0.9477 - val_acc: 0.4369
Epoch 47/50
373s - loss: 0.4522 - acc: 0.8368 - val_loss: 0.7415 - val_acc: 0.7046
Epoch 48/50
375s - loss: 0.3635 - acc: 0.8499 - val_loss: 1.1013 - val_acc: 0.4308
Epoch 49/50
377s - loss: 0.3853 - acc: 0.8229 - val_loss: 0.8241 - val_acc: 0.5815
Epoch 50/50
387s - loss: 0.3360 - acc: 0.8514 - val_loss: 0.7930 - val_acc: 0.6123
Training duration : 18244.619978
Network's test score [loss, accuracy]: [0.79298814260042627, 0.61230769267449014]
CNN Error: 38.77%
<matplotlib.figure.Figure at 0x123061b50>

In [25]:
bc.save_model(dir_path='./weights/', model=cnn_model, name='jn_Core_CNN_Diagnosis_20170530')


Model and Weights Saved to Disk

In [26]:
print 'Core CNN Accuracy: {: >7.2f}%'.format(test_score_cnn[1] * 100)
print 'Core CNN Error: {: >10.2f}%'.format(100 - test_score_cnn[1] * 100)

predictOutput = bc.predict(cnn_model, X_test)

cnn_matrix = skm.confusion_matrix(y_true=[val.argmax() for val in Y_test], y_pred=predictOutput)

plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(cnn_matrix, classes=class_names, normalize=True,
                         title='CNN Normalized Confusion Matrix Using Differencing \n')
plt.tight_layout()
plt.savefig('../../figures/jn_Core_CNN_Diagnosis_20170530.png', dpi=100)


Core CNN Accuracy:   61.23%
Core CNN Error:      38.77%
Normalized confusion matrix
[[ 0.8   0.2 ]
 [ 0.57  0.43]]

In [27]:
plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(cnn_matrix, classes=class_names, normalize=False,
                         title='CNN Raw Confusion Matrix Using Differencing \n')
plt.tight_layout()


Confusion matrix, without normalization
[[129  33]
 [ 93  70]]

In [28]:
bc.cat_stats(cnn_matrix)


Out[28]:
{'Accuracy': 61.23,
 'F1': 0.53,
 'NPV': 58.11,
 'PPV': 67.96,
 'Sensitivity': 42.94,
 'Specificity': 79.63}

In [ ]: